import { calc, cubicIn, cubicInOut, cubicOut, hold } from '../../../common/animation';
import { gradientColor } from '../../../common/color/color';
import { BaseComp } from '../../../common/component/comp';
import { ZRenderComp } from '../../../common/component/zrComp';

import { fetchAvatarUrl, waitForFetchingAvatar } from '../utils';

const layout = {
	global : {
		width : 1920,
		height : 67
	},
	slider : {
		circle : {
			r : 21,
			x : 27,
			y : 33,
		},
		tRect : {
			fromHeight : 21,
			fromX : 40,
			fromY : 22,
			toHeight : 4,
			toX : 2,
			toY : 2,
		},
		bRect : {
			fromHeight : 21,
			fromX : 40,
			fromY : 22,
			toHeight : 4,
			toX : 2,
			toY : 60,
		},
		userName : {
			fromX : 58,
			fromY : 43,
			toX : 63,
			toY : 30,
		},
		text : {
			x : 63,
			y : 57
		}
	},
	color : {
		premium : '#F0FA12',
		second : '#3FFEEE'
	}
};

class Panel extends BaseComp {
	/** @type {HTMLImageElement[]} */
	images = [];
	/** @type {Slider[]} */
	sliders = [];

	constructor(args) {
		super();
		this.context = args.context;
		this.zrender = args.zrender;
		this.initDom();

		this.x = 0;
		this.y = 0;

		this.startTick = this.startTick.bind(this);
	}

	initDom() {
		// 先要创建 Dom
		const main = document.getElementById('main');
		const div = document.createElement('div');
		div.id = 'panel';
		div.style.position = 'absolute';
		div.style.overflow = 'hidden';
		div.style.width = layout.global.width + 'px';
		div.style.height = layout.global.height + 'px';
		div.style.left = '0px';
		div.style.top = '0px';
		div.style.background = '#0003';
		main.appendChild(div);
		this.zr = this.zrender.init(div);
	}

	onDanmuReceived(o) {
		this.createSlider(o.uName, o.uId, o.text);
	}

	init() {
		this.lastHandler = requestAnimationFrame(this.startTick);
	}

	startTick() {
		this._tick();
	}

	createSlider(user, uid, text) {
		const slider = new Slider({
			parent : this,
			zrender : this.zrender,
			zr : this.zr,
			x : 0, y : 0,
			user, uid, text
		});
		const offset = slider.width + 40;
		for (let i = this.sliders.length - 1; i >= 0; i--) {
			const s = this.sliders[i];
			s.targetX += offset;
			s.getAnimX().update().when(0, s.x).when(500, s.targetX).easing(cubicOut).commit();
		}
		this.sliders.push(slider);
	}

	destroy() {
		super.destroy();
		cancelAnimationFrame(this.lastHandler);
		if (this.zr) {
			this.zr.dispose();
		}
		let dom = document.getElementById('panel');
		if (dom) {
			dom.remove();
		}
	}

	tick(now) {
		super.tick(now);
		this.lastHandler = requestAnimationFrame(this.startTick);
	}
}

class Slider extends ZRenderComp {
	constructor(args) {
		super(args.parent, args.zrender);
		this.useExistZRenderInstance(args.zr, args.x, args.y);

		/** @type {string} 昵称 */ this.user = args.user;
		/** @type {number} ID */ this.uid = args.uid;
		/** @type {string} 弹幕 */ this.text = args.text;
		this.targetX = 0;
		this.startTime = new Date().getTime();

		/**
		 * 0: 刚创建, 展示动画, 出现左边的圈、发弹幕的人和长方形
		 * 1: 等待
		 * 2: 动画, 长方形拆出俩, 一个在上一个在下, 显示弹幕文本
		 * 3: 正常播放
		 * 4: 动画: 变暗
		 * 5: 变暗后播放
		 * 6: 动画: 消失. 可以从 3 阶段直接到 6 阶段. TODO
		 */
		this.phase = -1;

		// 根据文本长度计算 width. 一旦算出来就是定值. width 含左边圆的部分, 所以不等于上下两长条的长度
		const l = layout.slider;
		this.spUserRemover = this.createText(this.user, 18)
			.blend('destination-out')
			.textStyle('left', 'bottom', 'bold', 'Microsoft YaHei UI')
			.position(l.userName.fromX, l.userName.fromY)
			.opacity(0)
			.z(0, 98)
			.scale(1, 1)
			.build();

		this.spText = this.createText(this.text, 22)
			.textStyle('left', 'bottom', 'bold', 'Microsoft YaHei UI')
			.textFill('white')
			.position(l.text.x, l.text.y)
			.opacity(0)
			.z(0, 9)
			.build();

		let bound1 = this.spUserRemover.getBoundingRect();
		let bound2 = this.spText.getBoundingRect();
		let width = Math.max(bound1.width, bound2.width);
		if (width > 280) {
			width = 280;
		}
		this.userScaleX = (bound1.width > width) ? width / bound1.width : 1;
		this.spUserRemover.scaleTo(this.userScaleX, 1);
		this.rollingEnable = bound2.width > width;
		this.textWidth = bound2.width;
		this.width = width + 72;

		this.rollingUpdatedTime = this.startTime + 5000;
		this.rolling = false;
	}

	init() {
		if (this.startTime - new Date().getTime() > 360000) {
			this.destroy();
			return;
		}

		const l = layout.slider;
		const perimeter = 2 * Math.PI * l.circle.r;
		this.spCircleStroke = this.createCircle(0)
			.fill('transparent')
			.stroke(layout.color.premium, 5)
			.shadow(layout.color.premium, 5)
			.lineDash([perimeter/6, perimeter/12], perimeter/12)
			.position(l.circle.x, l.circle.y)
			.opacity(0)
			.z(1, 1)
			.build();

		this.spTRect = this.createRect(0, l.tRect.fromHeight)
			.fill(layout.color.premium)
			.shadow(layout.color.premium, 5)
			.position(l.tRect.fromX, l.tRect.fromY)
			.z(0, 1)
			.build();
		this.spBRect = this.createRect(0, l.bRect.fromHeight)
			.fill(layout.color.premium)
			.shadow(layout.color.premium, 5)
			.position(l.bRect.fromX, l.bRect.fromY)
			.z(0, 1)
			.build();
		this.spCircleRemover = this.createCircle(l.circle.r)
			.blend('destination-out')
			.position(l.circle.x, l.circle.y)
			.z(0, 99)
			.build();

		this.spUserClip = this.createRect(10, 20, 0, -20) // x 会被下面的动画重置
			.defineAsClip()
			.scale(1 / this.userScaleX, 1)
			.build();
		this.spUserRemover.setClipPath(this.spUserClip);

		this.spUserText = this.createText(this.user, 18)
			.textStyle('left', 'bottom', 'bold', 'Microsoft YaHei UI')
			.textFill(layout.color.second)
			.position(l.userName.fromX, l.userName.fromY)
			.opacity(0)
			.z(0, 97)
			.scale(this.userScaleX, 1)
			.build();
		this.spUserText.setClipPath(this.spUserClip);

		createAvatar(this.uid).getPromise().then(canvas => {
			this.spImage = this.createImage(canvas)
				.position(l.circle.x, l.circle.y)
				.z(1, 0)
				.anchor(32, 32)
				.scale(this.spCircleStroke.sp.style.opacity) // 值等大小
				.build();
		});

		if (this.rollingEnable) {
			const showWidth = this.width - l.userName.fromX - 10;
			this.spText1Clip = this.createRect(showWidth, 30, 0, -30)
				.defineAsClip()
				.build();
			this.spText.setClipPath(this.spText1Clip);

			this.spNextText = this.createText(this.text, 22)
				.textStyle('left', 'bottom', 'bold', 'Microsoft YaHei UI')
				.textFill('white')
				.position(l.text.x, l.text.y)
				.z(0, 9)
				.build();
			this.spText2Clip = this.createRect(showWidth, 30, 0, -30)
				.position(-showWidth, 0)
				.defineAsClip()
				.build();
			this.spNextText.setClipPath(this.spText2Clip);
			this.rollDelta = 0;
		}

		this.updatePhase(0);

		// 动画部分
	}

	updatePhase(phase) {
		this.phase = phase;
		this.clearAnimations(name => name != 'x'); // 保留 x 的动画
		this.x = this.targetX;
		switch (phase) {
			case 0: this.phase0Animate(); break;
			case 1: this.phase1Animate(); break;
			case 2: this.phase2Animate(); break;
			case 3: this.phase3Animate(); break;
			case 4: this.phase4Animate(); break;
			case 5: break;
		}
	}

	phase0Animate() {
		const l = layout.slider;
		const perimeter = 2 * Math.PI * l.circle.r;

		this.createAnimation('circle-rotate', perimeter/12)
			.whenUpdated(p => this.spCircleStroke.attr({ style : { lineDashOffset : p.value } }))
			.update()
			.when(0, perimeter/12)
			.when(500, perimeter/3).easing(cubicInOut)
			.commit();
		this.createAnimation('circle-opacity', 0)
			.whenUpdated(p => {
				this.spCircleStroke.attr({
					shape : { r : l.circle.r * p.value },
					style : { opacity : p.value }
				});
				this.spImage && this.spImage.scaleTo( p.value );
			})
			.update()
			.when(0, 0)
			.when(500, 1).easing(cubicOut)
			.commit();

		const toWidth = this.width - l.bRect.fromX;
		this.createAnimation('rect-width-1', 0)
			.whenUpdated(p => {
				this.spTRect.attr({ shape : { width : p.value } });
				this.spBRect.attr({ shape : { width : p.value } });
				this.spUserRemover.moveTo(l.userName.fromX - (toWidth - p.value), l.userName.fromY);
				this.spUserText.moveTo(l.userName.fromX - (toWidth - p.value), l.userName.fromY);
				this.spUserClip.attr({ shape : { width : p.value + 10, x : toWidth - p.value - 10 } });
			})
			.update()
			.when(500, 0).callback(() => this.spUserRemover.opacity(0.9))
			.when(1100, toWidth).easing(cubicOut).callback(() => this.updatePhase(1))
			.commit();
	}

	phase1Animate() {
		this.createAnimation('wait', 0)
			.update()
			.when(1800, 1).easing(hold).callback(() => this.updatePhase(2))
			.commit();
	}

	phase2Animate() {
		const l = layout.slider;

		// 两个矩形
		this.createAnimation('rect-x', l.tRect.fromX)
			.whenUpdated(p => {
				this.spTRect.moveTo(p.value);
				this.spTRect.attr({ shape : { width : this.width - p.value } });
				this.spBRect.moveTo(p.value);
				this.spBRect.attr({ shape : { width : this.width - p.value } });
			})
			.update()
			.when(300, l.tRect.fromX)
			.when(500, l.tRect.toX).easing(cubicInOut)
			.commit();

		const startTy1 = l.tRect.fromY;
		const startBy1 = l.bRect.fromY;
		const startTy2 = startTy1 + l.tRect.fromHeight;
		const endTy1 = l.tRect.toY;
		const endBy1 = l.bRect.toY;

		this.createAnimation('rect-height', 0)
			.whenUpdated(p => {
				this.spTRect.attr({ shape : { height : p.value } });
				this.spBRect.attr({ shape : { height : p.value } });
			})
			.update()
			.when(0, l.tRect.fromHeight)
			.when(200, startTy2 - endTy1).easing(cubicIn)
			.when(400, startTy2 - endTy1).easing(hold)
			.when(600, l.tRect.toHeight).easing(cubicOut)
			.commit();

		this.createAnimation('rect-top-y', 0)
			.whenUpdated(p => this.spTRect.moveTo(undefined, p.value))
			.update()
			.when(0, startTy1)
			.when(200, endTy1).easing(cubicIn)
			.commit();

		this.createAnimation('rect-bottom-y', 0)
			.whenUpdated(p => this.spBRect.moveTo(undefined, p.value))
			.update()
			.when(400, startBy1)
			.when(600, endBy1).easing(cubicOut).callback(() => this.updatePhase(3))
			.commit();

		// 文字
		this.createAnimation('text-animation', 0)
			.whenUpdated(p => {
				this.spUserText.moveTo(
					calc(l.userName.fromX, l.userName.toX, p.value),
					calc(l.userName.fromY, l.userName.toY, p.value)
				);
				this.spUserRemover.moveTo(
					calc(l.userName.fromX, l.userName.toX, p.value),
					calc(l.userName.fromY, l.userName.toY, p.value)
				);
				this.spUserText.attr({ style : { opacity : p.value * 0.6 } });
				this.spUserRemover.attr({ style : { opacity : 1 - p.value } });
				this.spText.attr({ style : { opacity : p.value } });
				this.spText.moveTo(undefined, calc(l.text.y - 10, l.text.y, p.value, cubicOut));

				let fill = gradientColor(layout.color.premium, layout.color.second, p.value).toHexRGB();
				this.spTRect.attr({ style : { fill, shadowColor : fill } });
				this.spBRect.attr({ style : { fill, shadowColor : fill } });
			})
			.update()
			.when(200, 0)
			.when(500, 1)
			.commit();
	}

	phase3Animate() {
		this.createAnimation('wait', 0)
			.update()
			.when(120000, 1).easing(hold).callback(() => this.updatePhase(4))
			.commit();
	}

	phase4Animate() {
		this.createAnimation('gray', 1)
			.whenUpdated(p => {
				this.spTRect.attr({ style : {opacity : p.value} });
				this.spBRect.attr({ style : {opacity : p.value} });
				this.spText.attr({ style : {opacity : p.value} });
				this.spUserText.attr({ style : {opacity : p.value * 0.3 + 0.3} });
				this.spNextText && this.spNextText.attr({ style : {opacity : p.value} });
				this.spCircleStroke.attr({ style : {opacity : p.value} });
				this.spImage.attr({ style : {opacity : p.value} });
			})
			.update()
			.when(50, 1).when(100, 0.3)
			.when(150, 1).when(200, 0.3)
			.when(250, 1).when(300, 0.3)
			.when(350, 1).when(400, 0.3)
			.when(450, 1).when(500, 0.3).callback(() => this.updatePhase(5))
			.commit();
	}

	tick(now) {
		if (this.x >= layout.global.width) {
			this.destroy();
			return;
		} else if (this.x + this.width >= layout.global.width) {
			this.becomeGray();
		}
		if (this.rollingEnable) {
			this.tickRolling(now);
		}
		super.tick(now);
	}

	paint() {
		super.paint();
		this.spCircleStroke.attr({ rotation : -this.x / layout.slider.circle.r });
	}

	tickRolling(now) {
		if (!this.rolling && now - this.rollingUpdatedTime >= 3000) {
			this.rolling = true;
		}
		if (!this.rolling) {
			return;
		}

		const x1 = --this.rollDelta;
		const x2 = x1 + this.textWidth + 80;
		const baseX = layout.slider.text.x;
		this.spText.moveTo(baseX + x1);
		this.spNextText.moveTo(baseX + x2);
		this.spText1Clip.moveTo(-x1);
		this.spText2Clip.moveTo(-x2);

		if (x2 > 0) {
			return;
		}

		this.rolling = false;
		this.rollingUpdatedTime = now;
		this.rollDelta = 0;
	}

	becomeGray() {
		if (this.phase >= 4) {
			return;
		}
		this.updatePhase(4);
	}
}

// uid - avatar : cache
const avatarMap = {};

/**
 * @returns {Avatar}
 */
function createAvatar(uid) {
  if (avatarMap[uid]) {
    return avatarMap[uid];
  }
  return avatarMap[uid] = new Avatar(uid);
}

class Avatar {
	/**
	 * @param {number} uid 
	 */
	constructor(uid) {
		this.uid = uid;
		this.path = fetchAvatarUrl(uid);
		this.ok = false;
		this.canvas = document.createElement('canvas');
		this.width = this.canvas.width = 64;
		this.height = this.canvas.height = 64;
		this.fail = false;
		const promise = waitForFetchingAvatar(uid);
		this.imagePromise = promise.promise;
		this.cancalFunc = promise.cancel;
	}

	getPromise() {
		if (this.ok) {
			return Promise.resolve(this.canvas);
		}
		if (this.fail) {
			this.fail = false;
			const promise = waitForFetchingAvatar(this.uid);
			this.imagePromise = promise.promise;
			this.cancalFunc = promise.cancel;
		}
		return this.imagePromise.then(image => {
			this.handleCanvas(image);
			return this.canvas;
		}).catch(e => {
			console.error(e);
			this.fail = true;
			return this.canvas;
		});
	}

	handleCanvas(image) {
		const {width, height} = this;
		const ctx2d = this.canvas.getContext("2d");
		ctx2d.drawImage(image, 0, 0, image.width, image.height, 5, 5, width-10, height-10);

		const r = layout.slider.circle.r - 4;
		ctx2d.globalCompositeOperation = 'destination-in';
		ctx2d.fillStyle = '#FFF';
		ctx2d.beginPath();
		ctx2d.ellipse(width/2, height/2, r, r, 0, 0, 2 * Math.PI);
		ctx2d.fill();
		this.ok = true;
	}

	cancel() {
		if (this.ok) {
			return;
		}
		this.cancalFunc();
	}
}

export {
	Panel
};
